package nl.haemers.data.serializers 
{
	import flash.errors.IllegalOperationError;
	import flash.utils.*;
	import flash.xml.XMLNode;
	import nl.haemers.data.reflection.IMethod;
	import nl.haemers.data.reflection.IMethods;
	import nl.haemers.data.reflection.IAccessor;
	import nl.haemers.data.reflection.IParameter;
	import nl.haemers.data.reflection.IParameters;
	import nl.haemers.data.reflection.IProperty;
	import nl.haemers.data.reflection.Type;
	
	import nl.haemers.utils.Logger;
	import nl.haemers.data.constants.DataTypes;
	import nl.haemers.data.reflection.Property;
	import nl.haemers.data.utils.ObjectUtils;
	/**
	 * @author Jonathan Haemers
	 * @description:
	 * - The xmlparser parses xml to DTO/VO 's. You put a DTO & some XML in the parser and it will return a nice DTO.
	 * - The parser looks for Serialize() attributes in the DTO's if they aren't there for specific properties it will
	 *   use the name of the properties. 
	 * - Nested DTO's in DTO's are possible
	 * - Parser creates Arrays & Vectors and all of the datatypes(Number, int, String etc)
	 * - Flow of the serializer: Loop through data and try to match each element with properties in the ClassReference
	 * 
	 * Functions:
	 * - 
	 * 
	 * PROBLEMS:
	 * 
	 * TODO:
	 * - Report method for the serializer.(When debug is true log failures etc)
	 * - custom collections 
	 * - multi parameters support for methods with Vector & Array params
	 * 	- build a mechanism to check if some values have been parsed already
	 * - add support for vars to the XML Serializer
	 *   - serialize option: default value for param
	 * - add support for constructor parameters
	 * - Optimalisation
	 ***** We need a pattern for the parsing it's double work now!!!****
	 * 
	 * DONE:
	 * - ignore option
	 * - check object serializer for vars
	 * - check object serializer for methods 
	 * - Support for arrays & vectors
	 * - multi parameters support for methods
	 * - multi parameters support for methods with object params
	 * 
	 * PROBLEMS SOLVED:
	 * - Solved with soluction 2
	 *   When we have an ObjectA in ObjectA as property/Vector the ObjectSerializer will crash as a result of an infinity loop
	 * 		Solution1 to the problem could be to define an max limit
	 * 		Solution2 to the problem could be to create an instance of the property when needed 
	 * 		(we need a check then in the ObjectSerializer if the property object != to the current object)
	 * 
	 */
	public class XMLSerializer extends BaseSerializer
	{
		protected var _regExColor			:RegExp = /^0x/i;
		protected var _properties			:Dictionary;
		
		public function XMLSerializer(debug:Boolean = true) 
		{
			super(debug);
		}
		
		/** Public methods **/
		public function deserialize(xml:XML, ClassReference:Class) : Array
		{
			var type:Type = ObjectSerializer.getType(new ClassReference());
			var result:Array = new Array();
			
			if (!type.instance)
			{
				Logger.w("Couldn't find a Serialize name!\n Add [Serialize(name=\"\")] to your object.]", this);
				throw new IllegalOperationError("Couldn't find a Serialize name!\n Add [Serialize(name=\"\")] to your object.]");
				return null;
			}
			
			for each(var node:XML in xml.elements(type.name))
			{
				//TODO: Maybe we need to optimize this. Every iteration we need a new Type instance. 
				result.push(parseComplexNode(node, new ClassReference(), ObjectSerializer.getType(new ClassReference())));
			}
			
			return result;
		}
		
		/** Protected methods **/
		//Parse node with nodes in it
		protected function parseComplexNode(node:XML, refObject:Object, type:Type) : Object
		{
			//first check for attributes:
			if (node.attributes().length() > 0)
			{
				refObject = parseAttributes(node, refObject, type);
			}
			
			for each(var childNode:XML in node.children())
			{
				if (childNode.hasComplexContent())
				{
					//need a pattern for this :)
					//next up is IVariable to check..... prob een strategy pattern
					
					var property:IProperty = type.getProperty(childNode.name());
					if(property && !property.ignore && property.writeable)
					{
						//this could mean 3 things
						//It is an array/vector/object
						//
						//property.dataType == DataTypes.ARRAY
						//parseobjects of the array but don't add them like properties but use the push method.
						//
						switch(property.dataType)
						{
							case DataTypes.ARRAY:
							case DataTypes.VECTOR:
							//TODO: fix this parsecollection method
								//refObject[accessor.name] = parseCollection(childNode, tempType.instance, tempType);
								//refObject = super.writeValueToObject(refObject, property.propertyName, parseCollection(childNode, property.objectType.instance, property.objectType));
								//refObject.push(parseComplexNode(childNode, property.objectType.instance, property.objectType));
								refObject[property.propertyName] = parseCollection(childNode, property.objectType.instance, property.objectType);
							break;
							default: 
								refObject[property.propertyName] = parseComplexNode(childNode, property.objectType.instance, property.objectType);								
							break;
						}
						
					}
					else
					{
						/**
						 * We have several options for the methods. 
						 * - First we check the params for the value we are looking for. 
						 * - Another option is to give the method a name in de VO and then we look for the ResourceNode
						 * which hold all the data. 
						 * 
						 * Most logical thing to do is to look at the name of the method. If the VO doesn't have a name
						 * which was set by the developer we look into the params. 
						 */
						var method:IMethod = type.getMethod(childNode.name());
						
						/**
						 * there is no property with the name BUT maybe there is a method that could handle this? 
						 * We can't look for a method with the same name it's not logical.  
						 * We can look in the methods for parameters with the same name though :)
						 */
						if (!method)
						{
							method = type.getMethodWithParam(childNode.name());
						}
						
						if (method && !method.ignore)
						{
							refObject = parseComplexMethod(childNode, refObject, type, method);
						}
						else
						{
							refObject = parseComplexNode(childNode, refObject, type);
						}
					}
				}
				else if(childNode.hasSimpleContent())
				{
					refObject = parseSimpleNode(childNode, refObject, type);
				}
			}
			
			return refObject;
		}
		//Not sure of the method!
		protected function parseComplexMethod(node:XML, refObject:Object, type:Type, method:IMethod) : Object
		{
			//values
				//node -> 
			/**
			 * First get all the parameters we need. 
			 * Then check which are really required and which are optional.
			 * If we have them all let's parse this stuff :)
			 */
			
			 /* TODO: Fix this method!
			  * We know the method eq(setVacationAddress)
			  * We CANNOT use the params of the method anymore to look for the right method.
			  * 
			  * I guess the most logic thing to do is only search from the childnode futher down.
			  * */
			 
			 // We only search on the same level as we found the childnode. It's the most logical thing to do I guess.
			var parent:XML = node.parent();
			var args:Array = parseParameters(node, method);
			
			if (args)
			{
				refObject[method.methodName].apply(refObject, args);
			}
			
			return refObject;
		}
		
		//Parse Collections
		//TODO: same code as parsecomplex stuff..
		protected function parseCollection(node:XML, refObject:Object, type:Type) : Object
		{
			//values 
				//node -> <pages>
				//refObject -> Array
				//type -> Type(Array type)
				
			//first check for attributes:
			if (node.attributes().length() > 0)
			{
				refObject = parseAttributes(node, refObject, type);
			}
			
			for each(var childNode:XML in node.children())
			{
				if (childNode.hasComplexContent())
				{
					var property:IProperty = type.getProperty(childNode.name());
					if (property)
					{
						switch(property.dataType)
						{
							case DataTypes.ARRAY:
							case DataTypes.VECTOR:
							//TODO: fix this parsecollection method
								//refObject[property.name] = parseMethod(childNode, property.objectType.instance, property.objectType);								
							break;
							default: 
								//refObject[property.name] = parseComplexNode(childNode, property.objectType.instance, property.objectType);								
							break;
						}
					}
					else
					{
						//refObject = parseComplexNode(childNode, new Object(), type);
						//refObject = parseComplexNode(childNode, type.createCollectionItem(), type.collectionItemType);
						refObject.push(parseComplexNode(childNode, type.createCollectionItem(), type.collectionItemType));
					}
				}
				else if(childNode.hasSimpleContent())
				{
					//refObject.push(parseSimpleNode(childNode, new Object(), type));
					refObject.push(parseSimpleNode(childNode, type.createCollectionItem(), type.collectionItemType));
				}
			}
			
			return refObject;
		}
		
		//parse collection
		/*
		protected function parseCollection(node:XML, refObject:Object, type:Type) : Object
		{
			var tType:Type = type.collectionType ? type.collectionType : type;
			for each(var childNode:XML in node.children())
			{
				var collectionType:Object = type.collectionType ? tType.instance : { };
				if (childNode.hasComplexContent())
				{
					refObject.push(parseComplexNode(childNode, collectionType, tType));
				}
				else
				{
					refObject.push(parseSimpleNode(childNode, collectionType, tType));
				}
			}
			
			return refObject;
		}
		*/
		/**
		 * Parse simple node to dataobject property. Or use a method to write it to the dataobject
		 * @param	node
		 * @param	refObject
		 * @param	type
		 * @return 	refObject
		 */
		protected function parseSimpleNode(node:XML, refObject:Object, type:Type) : Object
		{
			refObject = parseAttributes(node, refObject, type);
			//Logger.i("Node name: " + node.name());
			
			
			return parse(refObject, type, node.name(), node.toString(), node);
		}
		
		/**
		 * Parse attributes to dataobject property. Or use a method to write the attributes to the dataobject.
		 * @param	node
		 * @param	refObject
		 * @param	type
		 * @return  refObject
		 */
		protected function parseAttributes(node:XML, refObject:Object, type:Type) : Object
		{
			//node has attributes let's check them out
			for each(var attr:XML in node.attributes())
			{
				//get attribute name
				var attrName:String = attr.name().toString();
				
				refObject = parse(refObject, type, attrName, attr.toString());
			}
			
			return refObject;
		}
		
		protected function parse(refObject:Object, type:Type, name:String, value:String, node:XML = null) : Object 
		{
			//We need to check if the node is already assigned to the VO!
			var isNodeAssigned:Boolean = false;
			if (type.hasProperty(name))
			{
				var prop:IProperty = type.getProperty(name);
				
				if (!prop.ignore && prop.writeable)
				{
					//refObject = super.writeValueToProperty(refObject, prop, parseValue(value, prop.dataType));
					refObject[prop.propertyName] = parseValue(value, prop.dataType);
					isNodeAssigned = true;
				}
			}
			
			if (!isNodeAssigned && type.hasMethodWithParam(name))
			{
				var method:IMethod = type.getMethodWithParam(name);
				if (!method.ignore)
				{ 
					var args:Array = parseParameters(node, method);
					
					if (args)
					{
						//nothing to parse so we don't call the method
						try
						{
							//TODO: method.name isn't the name of the method to be called.
							//it's the name of the serialize name!
							//this ain't working. If we need more parameters we get an error.
							//Solution:
							// - Find the missing parameters in this xml parent if they aren't here fail this method
							//Problem:
							// -  If we gonna make the above solution we need to flag the xml for the parameters!
							//		Otherwise there is a posibility we are gonna call this methods x times.(x stands for the 
							//		amount of parameters we have.
							refObject[method.methodName].apply(refObject, args);
							isNodeAssigned = true;
						}
						catch (e:Error)
						{
							Logger.w(e, this);
						}
					}
				}
			}
			
			if(!isNodeAssigned && type.isDynamic)
			{
				refObject[name] = parseValue(value, ObjectUtils.getDataType(value));
				isNodeAssigned = true;
			}
			
			return refObject;
		}
		
		//TODO: Add parse object functionality..
		protected function parseParameters(node:XML, method:IMethod) : Array
		{
			var params:Dictionary = method.parameters.getParameters();
			var args:Array = new Array();
			for (var key:String in params)
			{
				var param:IParameter = params[key];
				var argValue:*;
				
				//Param is an object so we need to parse this differently
				if (param.objectType)
				{
					argValue = parseComplexNode(node, param.objectType.createInstance(), param.objectType);
				}
				else
				{
					var name:String = param.name;
					var nodeValue:String = null;
					var parent:XML = node.parent();
					
					if (node.hasOwnProperty(name))
					{
						//grab the first child
						nodeValue = node.child(name)[0].toString();
					}
					else if (parent != null && parent.hasOwnProperty(name))
					{
						nodeValue = parent.child(name)[0].toString();
					}
					
					argValue = parseValue(nodeValue, ObjectUtils.getDataType(nodeValue));
				}
				//When we couldn't find a good value for the argument & the argument isn't optional we return null 
				//and we don't parse the parameters & method.
				//Add NaN check!
				if (!param.optional && (argValue == null || (nodeValue != null && ObjectUtils.isNumerical(nodeValue) && isNaN(argValue))))
				{
					Logger.w("Method: "+ method.name +" couldn't find all the required parameters!", this);
					return null;
				}
				//Tricky but it works :)
				args[param.index - 1] = argValue;
			}
			
			return args;
		}
		
		/** Private methods **/
	}
	
}